home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / pascal / execswap.zip / EXECSWAP.DOC < prev    next >
Text File  |  1989-04-04  |  18KB  |  343 lines

  1.                           More Memory for DOS Exec
  2.                                 Kim Kokkonen
  3.  
  4. As many have lamented, the 640K of memory available to DOS programs is looking
  5. smaller every year. With TSR's gobbling up memory on one end, and our
  6. applications growing larger on the other, it is easy to use up all the space
  7. and then some. Of course, necessity is the mother of invention, so desperate
  8. DOS programmers have devised a number of ad hoc methods to cram more functions
  9. into the same space -- by using expanded and extended memory, overlays, and so
  10. on.
  11.  
  12. This article describes another such method. We've enhanced the DOS Exec
  13. function by swapping most of the calling program into expanded memory or to
  14. disk, and giving all that free memory to the child process. When the
  15. subprocess is complete, the calling program is swapped back into place and
  16. continues normally. This technique is especially valuable for menuing
  17. environments which must execute other large programs, or modern programming
  18. editors which are expected to spawn huge compilations at the touch of a key.
  19. In fact, it's useful for any program that must invoke another.
  20.  
  21. The swapping Exec function is implemented in a Turbo Pascal 5.0 unit called
  22. ExecSwap. The real meat of the code is written in assembly language, however,
  23. and with some changes could be linked into other languages such as C or
  24. Fortran.
  25.  
  26. Turbo Pascal Program Organization
  27. ---------------------------------
  28. To explain how ExecSwap works, we'll need to delve into the organization of a
  29. Turbo Pascal program. Let's examine the program shown in Figure 1. What this
  30. program (named X) does isn't important. We'll just use it to show the
  31. arrangement of memory. X uses two of Turbo's standard units, Crt and Dos. It
  32. also implicitly uses the System unit, as does every Turbo Pascal program.
  33.  
  34. Figure 2 maps out the various segments. (You can see a similar map of a real
  35. program by having the compiler create a MAP file and inspecting the segment
  36. map at the beginning of that file.) It's important to note that each Pascal
  37. unit has its own code segment (denoted by CS_xxx in Figure 2), and that the
  38. code segments are arranged in what might seem like reverse order. That is, the
  39. unit appearing first in the USES statement is linked at the highest memory
  40. address, while the main program has the lowest code segment. If the program
  41. doesn't need to use the heap, the memory above the heap base may not be
  42. allocated.
  43.  
  44. Figure 1: Example Program
  45.  
  46.       program X;
  47.       uses {System,} Dos, Crt;
  48.       begin
  49.         ClrScr;
  50.         Exec('C:\COMMAND.COM', '');
  51.       end.
  52.  
  53. Figure 2: Memory Map of Example Program
  54.  
  55.       PSP:           program segment prefix            lower addresses
  56.       CS_X:          X code                                   |
  57.       CS_Crt:        Crt code                                 |
  58.       CS_Dos:        Dos code                                 v
  59.       CS_System:     System code                       higher addresses
  60.       DS:            initialized data                         |
  61.                      uninitialized data                       |
  62.       SS:            stack                                    v
  63.       HeapOrg:       heap base
  64.       HeapPtr:       heap high water mark
  65.                      available heap space
  66.       FreePtr:       free list
  67.       FreePtr+1000h: top of program
  68.                      available DOS memory
  69.       xxxx:          top of memory
  70.  
  71. ExecSwap's goal is to copy most of the memory used by the program to secondary
  72. storage and then to deallocate that memory. ExecSwap needs to leave only
  73. enough of itself behind to call DOS Exec and restore the image when the child
  74. process returns.
  75.  
  76. By this criterion, the best place for ExecSwap's code would be in the main
  77. body of the program. In this way, it could start swapping memory at the lowest
  78. possible code segment and free the most memory for the child process. In
  79. Figure 2's terms, it would start swapping at code segment CS_X and continue to
  80. the top of the program. After deallocating memory, the only overhead would be
  81. the program segment prefix (256 bytes) plus the portion of segment CS_X
  82. required to undo the swap. Figure 3 shows what memory might look like while
  83. the child process was active. The rest of program X would have been stored in
  84. EMS memory if available, or in a disk file if not.
  85.  
  86. Figure 3: Memory Map while Child Process is Active
  87.  
  88.       PSP:           program segment prefix      | ExecSwap
  89.       CS_X:          X code (partial)            | overhead
  90.         .--------------------------------------------------
  91.         |  child program  program segment prefix
  92.         |  ...
  93.         |  xxxx:          top of memory
  94.  
  95. There's another factor to consider, though. ExecSwap should be convenient to
  96. use in more than just one program. Hence, we've made it a self-contained unit
  97. which is available just by adding it to the main program's USES statement.
  98. Considering Figure 2 again, it's clear that when we USE ExecSwap we want to
  99. add it at the very end of the list. In that case, the memory map will look
  100. like Figure 4. The memory that remains allocated during the Exec is the PSP,
  101. the code in the main program X, and whatever part of ExecSwap must remain
  102. resident.
  103.  
  104. Figure 4: Memory Map after using ExecSwap
  105.  
  106.       PSP:           program segment prefix
  107.       CS_X:          X code
  108.       CS_ExecSwap:   ExecSwap code               <-----------
  109.       CS_Crt:        Crt code
  110.       CS_Dos:        Dos code
  111.       CS_System:     System code
  112.       ...
  113.       xxxx:          top of memory
  114.  
  115. The main program's code segment need not be very large, of course. In the
  116. extreme case, the main program would consist of nothing but a USES statement
  117. and a single procedure call to another unit. This reduces the overhead of
  118. the Exec call to essentially just the PSP plus ExecSwap itself. And that's not
  119. much: ExecSwap's resident portion consumes less than 2000 bytes.
  120.  
  121. Using ExecSwap
  122. --------------
  123. Before we plunge into the mechanics of ExecSwap, we'll describe how it is
  124. used by an application. The unit interfaces three routines, shown in Figure 5.
  125. Before performing an Exec call, the program must call InitExecSwap. This
  126. routine computes how many bytes to swap and allocates space to store the
  127. swapped region.
  128.  
  129. Figure 5: ExecSwap Routines
  130.  
  131.   function InitExecSwap(LastToSave : Pointer; SwapFileName : String) : Boolean;
  132.     {-Initialize for swapping, returning TRUE if successful}
  133.  
  134.   function ExecWithSwap(Path, CmdLine : String) : Word;
  135.     {-DOS Exec supporting swap to EMS or disk}
  136.  
  137.   procedure ShutdownExecSwap;
  138.     {-Deallocate swap area}
  139.  
  140. The swapped region of memory starts just beyond the resident portion of
  141. ExecSwap. The programmer must specify the _end_ of the region with the
  142. parameter LastToSave, since the choice depends on how the program uses the
  143. heap. What we choose for LastToSave affects only the size of the swap file, or
  144. the amount of EMS memory needed, but has no effect on resident overhead during
  145. the Exec call.
  146.  
  147. There are three reasonable values for LastToSave. Passing the System variable
  148. HeapOrg tells ExecSwap not to save any part of the heap; this is the correct
  149. option for programs that make no use of the heap. Passing HeapPtr causes
  150. ExecSwap to save all allocated portions of the heap. Only the free list is
  151. ignored, so this is a good choice for programs that don't fragment the heap.
  152. Passing the expression Ptr(Seg(FreePtr^)+$1000, 0) tells ExecSwap to save the
  153. entire heap, including the free list. This is the most conservative option,
  154. but it may lead to swap files approaching 640K bytes in size.
  155.  
  156. InitExecSwap's second parameter, SwapFileName, specifies the name and location
  157. of the swap file. If EMS memory is available, this name won't be used, but
  158. otherwise InitExecSwap will create a new file. InitExecSwap assures that
  159. sufficient EMS or disk space exists for the swap, otherwise it returns FALSE.
  160. It's a good idea, of course, to put the swap file on the fastest drive that
  161. will hold it, to minimize swap times. It's also prudent to avoid a floppy
  162. drive, since the user may change disks while the child process is active. The
  163. swap file remains open, using a file handle, until ShutdownExecSwap is called
  164. or the program ends. InitExecSwap marks the file with the Hidden and System
  165. attributes so that the user of the child process won't be tempted to delete
  166. it.
  167.  
  168. ExecWithSwap is analogous to the standard Exec procedure in Turbo's Dos unit.
  169. Its first parameter is the pathname of the program to execute, and the second
  170. is the command line to pass to it. The only difference from Exec is that
  171. ExecWithSwap is a function, returning the status of the call in a Word. The
  172. function returns DOS error codes, with one exception. Figure 6 lists the most
  173. common codes.
  174.  
  175. Figure 6: ExecWithSwap Error Codes
  176.  
  177.   0  Success
  178.   1  Swap error (no swap storage, disk error, EMS error)
  179.   2  File not found
  180.   3  Path not found
  181.   8  Insufficient memory
  182.  
  183. You may never need to call ShutdownExecSwap, since ExecSwap sets up an exit
  184. handler that automatically calls it when the program ends. In some cases,
  185. however, you may want to close and erase the swap file or regain EMS space
  186. before continuing.
  187.  
  188. There's a small conundrum here. We've said ExecSwap should be last in the USES
  189. list, and we also want the main program to do as little as possible. So where
  190. do we place calls to the ExecSwap routines? It's easiest to call them from the
  191. main program, and take the hit in overhead. Turbo Pascal provides a better key
  192. to the puzzle, though. Version 5 supports procedure variables, and version 4
  193. makes it easy to fake them. So what we do is this: in the main program, assign
  194. the address of each ExecSwap procedure to a procedure variable declared in a
  195. unit used early in the USES list. Then call ExecSwap's routines in any later
  196. unit by referring to the procedure variables.
  197.  
  198. One caution about using ExecSwap: since most of your program's code isn't in
  199. memory while the child process runs, it's essential that the program's
  200. interrupt handlers be deactivated first. Turbo Pascal 5 provides a handy
  201. procedure called SwapVectors that does this for all the System interrupt
  202. handlers. Call SwapVectors just before and after ExecWithSwap, and treat any
  203. of your own handlers in a similar fashion.
  204.  
  205. Listing 1 offers a simple example of using ExecSwap. You can assemble
  206. EXECSWAP.ASM (Listing 3) using MASM 4.0 or later, or any compatible assembler.
  207. Then compile the test program to an EXE file and run it, and you'll enter a
  208. DOS shell. If you have a DOS memory mapping utility, you'll see that the TEST
  209. program is using less than 3K of memory. The swap file uses about 20K, most of
  210. that for the 16K stack which is Turbo's default. If the swap goes to EMS, the
  211. EMS block will be 32K bytes, since EMS is allocated in 16K chunks. Type Exit
  212. to leave the shell and the test program will regain control.
  213.  
  214. A real program provides more impressive results. We developed ExecSwap for use
  215. in our Turbo Analyst product, which offers an integrated environment where the
  216. programmer can edit source files, then Exec the compiler, debugger, or any
  217. of many other programming utilities. Without benefit of ExecSwap, the
  218. environment keeps about 250K of memory during the Exec. With ExecSwap, the
  219. overhead is only about 4K. That 246K makes a huge difference!
  220.  
  221. How It's Done
  222. -------------
  223. ExecSwap's Pascal source file, EXECSWAP.PAS, is given in Listing 2. It's
  224. little more than a shell for the assembly language routines in EXECSWAP.ASM,
  225. Listing 3.
  226.  
  227. Looking at InitExecSwap in Listing 2, you'll see that it checks first for EMS
  228. memory (any version of EMS will do). If that is available, it is used in
  229. preference to disk storage. If not, InitExecSwap goes on to assure that
  230. there's enough space on the specified drive to hold the swap area. In our
  231. production version of ExecSwap (trimmed here for the sake of brevity), we
  232. check that the drive doesn't hold removable media. InitExecSwap also stores
  233. several items in global variables where they're easily accessible by the
  234. assembly language routines, and installs an exit handler to clean up after
  235. itself in case the program halts unexpectedly.
  236.  
  237. The tricky stuff is in EXECSWAP.ASM. The file starts with the standard
  238. boilerplate needed for linking to Turbo Pascal. We declare a number of
  239. temporary variables in the code segment; these are essential because the
  240. entire data segment is gone during critical portions of ExecWithSwap. One of
  241. these variables is a temporary stack. It's a small one, only 128 bytes, but it
  242. is required since the normal Turbo Pascal stack is also swapped out. Macro
  243. definitions follow; we've used more than our usual number of macros to keep
  244. the listing to a reasonable length.
  245.  
  246. ExecWithSwap starts by copying a number of variables into the code segment.
  247. Then it checks to see whether swapping will go to EMS or disk. If neither has
  248. been activated, ExecWithSwap exits immediately, returning error code 1.
  249. Otherwise, ExecWithSwap processes one of four similar loops: one each to swap
  250. to or from disk or EMS storage. Let's trace the "swap to EMS" loop in detail,
  251. at label WriteE. The sequence for swapping to disk is so similar that we won't
  252. need to describe it here.
  253.  
  254. We first map EMS memory, making the first 16K page of the EMS swap area
  255. accessible through the page window at FrameSeg:0. (Note that ExecSwap doesn't
  256. save the EMS context; if your application uses EMS for other storage, be sure
  257. to remap EMS after returning from ExecWithSwap.) The macro SetSwapCount then
  258. computes how many bytes to copy into the first page, returning a full 16K
  259. bytes unless it's also the last page. The first location to save is at label
  260. FirstToSave, which immediately follows the ExecWithSwap routine. The MoveFast
  261. macro copies the first swap block into the EMS window. BX is then incremented
  262. to select the next logical EMS page, and the DS register is adjusted to point
  263. to the next swap block, 16K bytes higher in memory. The loop continues until
  264. all the bytes have been copied to EMS.
  265.  
  266. Next we must modify the DOS memory allocation, so that the space just swapped
  267. out is available to the child process. First we save the current allocated
  268. size so we can restore it later. Then we switch to the small temporary stack
  269. which is safely nestled in the code segment, and finally call the DOS SetBlock
  270. function to shrink our memory to just beyond the end of the ExecWithSwap
  271. routine.
  272.  
  273. The actual DOS Exec call follows. The implementation here is similar to the
  274. one in Borland's Dos unit. It validates and formats the program path and
  275. command line, parses FCB's (file control blocks) from the command line in case
  276. the child expects them, and calls the DOS Exec function. The error code
  277. returned by Exec is stored until the reverse swap is complete.
  278.  
  279. The reverse swap is just that: it reallocates memory from DOS and copies the
  280. parent program back into place. There is one critical difference from the
  281. first swap, however. Errors that occur during the reverse swap are fatal.
  282. Since the program to return to no longer exists, our only recourse is to
  283. halt. The most likely reason for such an error is the inability to reallocate
  284. the initial memory block. This occurs whenever the Exec call (or the user) has
  285. installed a memory resident program while in the shell. Be sure to warn your
  286. users not to do this! ExecSwap could write an error message before halting;
  287. to save space here, we've just set the ErrorLevel, which can be checked within
  288. a batch file:
  289.  
  290.   0FFh     can't reallocate memory
  291.   0FEh     disk error
  292.   0FDh     EMS error
  293.  
  294. ExecWithSwap is done after it switches back to the original stack, restores
  295. the DS register, and returns the status code.
  296.  
  297. The remainder of EXECSWAP.ASM is a collection of small utility routines, some
  298. of which may find general use in your library.
  299.  
  300. In Summary
  301. ----------
  302. ExecSwap seems quite reliable. It doesn't depend on any newly discovered
  303. undocumented features of DOS, and has been tested by thousands of our
  304. products' users.
  305.  
  306. There are a few additional features it might have. Our production version
  307. writes status messages while swapping, so nervous users don't think their hard
  308. disks are being formatted. It might also support direct swapping to extended
  309. memory -- we haven't done so because experience indicates that using extended
  310. memory in a DOS application is a compatibility nightmare, and RAM disks seem
  311. quite adequate for swapping. If the remainder of ExecSwap were converted to
  312. assembly language, Turbo Pascal's link order conventions (within a unit) could
  313. be circumvented and another 500 bytes or so of Exec overhead would be saved.
  314. With a few more DOS memory management calls, it would be possible for the
  315. parent and child processes to share a common data area. Finally, an extension
  316. of the ExecSwap concept allows TSR programs to leave just a core of interrupt
  317. handlers in memory, and swap the application code in when they pop up
  318. (SideKick Plus apparently does this).
  319.  
  320. The ExecSwap unit has become a very useful item in our bag of tricks.
  321. With an ExecSwap-based DOS shell in the programming editor we use, we can
  322. achieve the kind of multitasking we need ("interruption-based" multitasking).
  323. ExecSwap should make it easier for you to squeeze more functionality into
  324. that 640K box as well.
  325.  
  326. Acknowledgement
  327. ---------------
  328. Special thanks to Chris Franzen of West Germany, who added disk swapping
  329. capability to our original unit, which supported only EMS.
  330.  
  331. This DOC file is an unedited version of an article that appeared in the April
  332. 1988 issue of Dr. Dobbs Journal.
  333.  
  334. About the Author
  335. ----------------
  336. Kim Kokkonen is the president of TurboPower Software, and the author of many
  337. public domain Turbo Pascal tools. He can be reached at P.O. Box 66747, Scotts
  338. Valley, CA 95066.
  339.  
  340. Listing 1: TEST.PAS
  341. Listing 2: EXECSWAP.PAS
  342. Listing 3: EXECSWAP.ASM
  343.